package org.jwd.storage.aop.dp;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jwd.core.anno.AuditRecord;
import org.jwd.core.consts.DictCategory;
import org.jwd.core.consts.OptType;
import org.jwd.core.entity.AuditEntity;
import org.jwd.core.entity.AuthEntity;
import org.jwd.core.utils.SpringContextUtils;
import org.jwd.storage.service.AuditService;
import org.jwd.storage.service.AuthService;
import org.jwd.storage.service.DataDictService;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author vizz
 * @since 2022/5/20 22:50
 */
@Slf4j
public class AuditSaveInterceptor implements MethodInterceptor {

    private OptType optType = null;
    private String entityClassSimpleName = null;
    private boolean paramIsId = false;

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        log.info("已进入审计动态代理对象！！！");
        Class<?> targetClass = methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
        Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
        if (!specificMethod.getDeclaringClass().equals(Object.class)) {
            final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
            final AuditRecord auditRecord = getAnnotation(method, targetClass, AuditRecord.class);
            if (auditRecord != null) {
                optType = auditRecord.optType();
                entityClassSimpleName = method.getParameterTypes()[0].getSimpleName();
                paramIsId = auditRecord.paramIsId();
            }
        }

        //2.继续回到之前的方法调用（因为可能要用到id，而id在insert执行后才能有）
        Object result = methodInvocation.proceed();

        //3.获取调用的参数
        Object[] args = methodInvocation.getArguments();
        Map<String, Object> map = new HashMap<>();
        if (paramIsId) {
            map.put("id", args[0]);
        } else {
            try {
                map = JSONObject.parseObject(JSON.toJSONString(args[0]));
            } catch (Exception e) {
                log.warn("对象转换失败，请检查方法入参是否为键值对象");
            }

        }

        // 这里获取请求头的信息。不出意外的话这段代码肯定能在shiro和spring security中看到，我先立个flag。
        // TODO：shiro和spring security有无类似代码？
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String authorization = request.getHeader("Authorization");//TODO：取session，前端注意完善一下

        //4.记录到审计表
        AuditService auditService = SpringContextUtils.getBean(AuditService.class);
        DataDictService dataDictService = SpringContextUtils.getBean(DataDictService.class);
        AuthService authService = SpringContextUtils.getBean(AuthService.class);

        AuthEntity authEntity = authService.findByValidation(authorization);
        Long optUserId = 0L;
        if (authEntity != null) {
            optUserId = authEntity.getUserId();
        }
        /**
         * private Long userId;
         * private String operate;//权限:级别
         * private String paramJson;//json格式的param对象
         * private String tableName;//影响了哪张表
         * private Long rowId;//影响了哪一列
         * private LocalDateTime operateTime;
         * private String ip;
         */
        Integer id = (Integer) map.get("id");
        AuditEntity auditEntity = new AuditEntity()
                .setUserId(optUserId)
                .setOperate(optType.name())
                .setParamJson(JSON.toJSONString(args))
                .setTableName(dataDictService.getByKeyAndCtg(entityClassSimpleName, DictCategory.ENTITY2TABLE))
                .setRowId(id.longValue())
                .setIp(request.getRemoteAddr());

        auditService.save(auditEntity);

        //5.原方法继续执行
        return result;
    }

    public <T extends Annotation> T getAnnotation(Method method, Class<?> targetClass, Class<T> annotationClass) {
        return Optional.ofNullable(method).map(m -> m.getAnnotation(annotationClass))
                .orElse(Optional.ofNullable(targetClass).map(t -> t.getAnnotation(annotationClass)).orElse(null));
    }
}
